﻿'''Contains all page classes (request handlers)'''

import os
import operator
import datetime
import errors
import urllib
from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from google.appengine.ext.webapp.util import run_wsgi_app
from models import *
from qproc import *
from validation import FieldParser
from translit import cyr_to_lat

import pytz
SOFIA_TZINFO = pytz.timezone('Europe/Sofia')

QUERY_PROCESSOR = QueryProcessorSKGT
#import tests
#QUERY_PROCESSOR = tests.QueryProcessorFake1

def get_response_page_values(qp, stop_numbers, service_patterns, time_limit, now=None, update_datastore=True):
    '''Return values to be displayed on the main 'request' page'''
    if now is None:
        now = datetime.datetime.now(SOFIA_TZINFO).replace(tzinfo=None)
    stops = []
    for stop_number in stop_numbers:
        try:
            stop_error = None
            stop = Stop(stop_number, qp, update_datastore)
            
            vehicles = []
            no_matching_lines = True
            for service in stop.services:
                if (len(service_patterns) == 0) or any(((s.upper() in service) for s in service_patterns)):
                    no_matching_lines = False
                    vehicles.extend([(service, v, (v - now).seconds // 60) for v in stop.vehicles(service)])
            if time_limit:
                vehicles = filter(lambda x: x[2] <= int(time_limit), vehicles)
            vehicles.sort(key=operator.itemgetter(2))
                        
            if no_matching_lines:
                stop_error = "No matching lines for [" + ", ".join(service_patterns) + "] on stop " + str(stop_number)
            elif len(vehicles) == 0:
                stop_error = "No vehicles are due " + (("in the next " + str(time_limit) + " minutes") if time_limit else "to arrive")

            stops.append({'error':stop_error, 'stop':str(stop_number).zfill(4), 'name':stop.name, 'name_lat':cyr_to_lat(stop.name), 'desc':stop.description, 'vehicles':vehicles})
        except errors.UnexistingStopError, e:
            stops.append({'error':str(e), 'stop':str(stop_number).zfill(4), 'name':None, 'name_lat':None, 'desc':None})
    
    template_values = {'now':now, 'stops':stops}
    return template_values

def get_response_template(headers, page):
    '''Return template filename for page and response content-type according to browser'''
    accept = headers.get('Accept', '')
    if accept.find('application/vnd.wap.xhtml+xml') >= 0:
        return ('templates/' + page + '-mobile-wap2.html', 'application/vnd.wap.xhtml+xml')
    elif accept.find('application/xhtml+xml') >=0:
        return ('templates/' + page + '-desktop-xhtml.html', 'application/xhtml+xml')
    else:
        return ('templates/' + page + '-desktop-html.html', 'text/html')

class CustomRequestHandler(webapp.RequestHandler):
    '''Base class for all request handlers'''
    def write_response(self, page, template_values):
        '''Render template corresponding to page and write the response '''
        (template_filename, content_type) = get_response_template(self.request.headers, page)
        path = os.path.join(os.path.dirname(__file__), template_filename)
        if os.path.isfile(path):
            self.response.headers['Content-Type'] = content_type
            page = template.render(path, template_values)
            pre = re.search(r'<pre>.*</pre>', page, re.DOTALL)
            page = re.sub(r'>\s+', '>', page)
            page = re.sub(r'\s+<', '<', page)
            if pre is not None: page = re.sub(r'<pre>.*</pre>', pre.group(0), page)
            self.response.out.write(page)
        else:
            tv = {'message':'The page is not supported on your device. Try opening it from a dekstop PC.'}
            self.write_response('404', tv)
        
class MainPage(CustomRequestHandler):
    '''Root page request handler'''
    def get(self):
        cookies = self.request.cookies
        field_stop = cookies.get('s', '')
        field_line = cookies.get('l', '')
        field_time = '60'
        template_values = {
            'error':None,
            'show_results': False,
            'field_stop': field_stop,
            'field_line': field_line,
            'field_time': field_time
            }
        self.write_response('req', template_values)

class ReqPage(CustomRequestHandler):
    '''Request page request handler'''
    def get(self):
        field_stop = self.request.get('s')
        field_line = self.request.get('l')
        field_time = self.request.get('t')

        template_values = {
            'field_stop':field_stop,
            'field_line':field_line,
            'field_time':field_time
        }
        
        try:
            stop_numbers = FieldParser.parse_field_stop(field_stop)
            service_patterns = FieldParser.parse_field_line(field_line)
            time_limit = FieldParser.parse_field_time(field_time)

            log = RequestLog()
            log.stop = field_stop
            log.line = field_line
            log.remote_addr = self.request.remote_addr
            log.url = self.request.url
            log.headers = repr(self.request.headers.items())
            log.put()

            qp = QUERY_PROCESSOR()
            template_values.update(get_response_page_values(qp, stop_numbers, service_patterns, time_limit))
            
        
            # add cookies; age = 10days
            self.response.headers.add_header('Set-Cookie', 's=%s; max-age=864000' % field_stop)
            self.response.headers.add_header('Set-Cookie', 'l=%s; max-age=864000' % field_line)
            
            template_values['show_results'] = True
        except errors.FieldValidationError, e:
            template_values['error'] = str(e)
        
        self.write_response('req', template_values)

class CsvPage(CustomRequestHandler):
    '''Csv page request handler'''
    def get(self):
        field_stop = self.request.get('s')
        field_line = self.request.get('l')
        field_time = self.request.get('t')

        try:
            stop_numbers = FieldParser.parse_field_stop(field_stop)
            service_patterns = FieldParser.parse_field_line(field_line)
            time_limit = FieldParser.parse_field_time(field_time)

            qp = QUERY_PROCESSOR()
            values = get_response_page_values(qp, stop_numbers, service_patterns, time_limit)
            stops = values['stops']
        except errors.FieldValidationError, e:
            template_values['error'] = str(e)

        output = ''
        for stop in stops:
            
            if stop['name'] is not None:
                output += ','.join([stop['stop'], stop['name'], stop['desc'], str(stop['error'])]) + '\n'
                if stop['error'] is None:
                    output += '\n'.join([','.join([v[0], v[1].strftime('%H:%M'), str(v[2])]) for v in stop['vehicles']]) + '\n'
                output += '\n'
        
        content_type = 'text/plain'
        self.response.headers['Content-Type'] = content_type
        self.response.out.write(output)

        
class AdminPage(CustomRequestHandler):
    '''Admin page request handler'''
    def get(self):
        requests = db.GqlQuery("SELECT * FROM RequestLog ORDER BY date DESC")
        for req in requests:
            date = db.DateTimeProperty(auto_now_add=True)
            remote_addr = db.StringProperty()
            url = db.StringProperty()
            headers = db.TextProperty()
            stop = db.StringProperty()
            line = db.StringProperty()
            
        list = ['%s\n    ip= %s\n    url= %s\n    headers= %s\n    stop= %s\n    line= %s\n'%(r.date,r.remote_addr,r.url,r.headers,r.stop,r.line) for r in requests]
        debug = '\n'.join(list)
        template_values = {
            'debug':debug
            }

        self.write_response('admin', template_values)

class UserEditPage(CustomRequestHandler):
    '''User edit page request handler'''
    def get(self):
        user = users.get_current_user()
        if user is None:
            self.redirect(users.create_login_url(self.request.uri))
            return
        nickname = user.nickname().lower()
            
        user_saved_pages = Bookmark.all().filter('user =', user).order('id')
        list_fields = map(lambda bm: (bm.id, bm.name, bm.pretty_stop_numbers, bm.pretty_service_patterns, bm.pretty_time_limit), user_saved_pages)
        template_values = {
            'list_fields':list_fields,
            'user_nickname':user.nickname(),
            'app_nickname':nickname,
            'logout_url':users.create_logout_url('/')
        }
        
        self.write_response('useredit', template_values)
        
    def post(self):
        self.response.headers['Content-Type'] = 'text/plain'
        user = users.get_current_user()
        nickname = user.nickname().lower()
        
        list_field_id = self.request.get_all('i')
        list_field_name = self.request.get_all('n')
        list_field_stop = self.request.get_all('s')
        list_field_line = self.request.get_all('l')
        list_field_time = self.request.get_all('t')
        i = 0
        while i < len(list_field_name):
            try:
                FieldParser.parse_field_pagename(list_field_name[i])
            except errors.InvalidFieldPageNameError:
                list_field_id.pop(i)
                list_field_name.pop(i)
                list_field_stop.pop(i)
                list_field_line.pop(i)
                list_field_time.pop(i)
            else:
                i += 1
        
        try:
            list_id = map(lambda s: int(s) if s else -1, list_field_id)
            list_name = map(FieldParser.parse_field_pagename, list_field_name)
            list_stop_numbers = map(FieldParser.parse_field_stop, list_field_stop)
            list_service_patterns = map(FieldParser.parse_field_line, list_field_line)
            list_time_limit = map(FieldParser.parse_field_time, list_field_time)
        
            pages = map(list, zip(list_id, list_name, list_stop_numbers, list_service_patterns, list_time_limit))

            user_saved_pages = Bookmark.all().filter('user =', user)
            for bm in user_saved_pages:
                bm.delete()
            
            for page in pages:
                if page[0] < 0:
                    m = max(zip(*pages)[0])
                    page[0] = m + 1
                bm = Bookmark(user=user, nickname=nickname, id=page[0], name=page[1], stop_numbers=page[2], service_patterns=page[3], time_limit=page[4])
                bm.put()

            message_ok = 'Your list has been saved.'
            message_error = None
            user_saved_pages = Bookmark.all().filter('user =', user).order('id')
            list_fields = map(lambda bm: (bm.id, bm.name, bm.pretty_stop_numbers, bm.pretty_service_patterns, bm.pretty_time_limit), user_saved_pages)
        except errors.FieldValidationError, e:
            message_ok = None
            message_error = str(e)
            list_fields = zip(list_field_id, list_field_name, list_field_stop, list_field_line, list_field_time)
            
        template_values = {
            'list_fields':list_fields,
            'user_nickname':user.nickname(),
            'app_nickname':nickname,
            'message_ok':message_ok,
            'message_error':message_error,
            'logout_url':users.create_logout_url('/')
        }
        self.write_response('useredit', template_values)
        
class UserBookmarksListPage(CustomRequestHandler):
    '''User's bookmarks list page request handler'''
    def get(self, nickname):
        nickname = urllib.unquote(nickname).lower()
        user_saved_pages = Bookmark.all().filter('nickname =', nickname).order('id')
        links = [(bm.name, '/u/' + nickname + '/' + str(bm.id)) for bm in user_saved_pages]
        template_values = {
            'links':links,
            'is_empty':(len(links) == 0)
            }
            
        self.write_response('userlist', template_values)
        
class UserBookmarkPage(CustomRequestHandler):
    '''User's bookmark page request handler'''
    def get(self, nickname, page_id):
        nickname = urllib.unquote(nickname).lower()
        bm = Bookmark.all().filter('nickname =', nickname).filter('id =', int(page_id)).get()
        if bm is None:
            self.error(404)
            template_values = {'message':'There is no saved page for nickname=' + nickname + ' with id=' + page_id}
            self.write_response('404', template_values)
            return
            
        template_values = {
            'show_results':True,
            'field_stop':bm.pretty_stop_numbers,
            'field_line':bm.pretty_service_patterns,
            'field_time':bm.pretty_time_limit
        }
        qp = QUERY_PROCESSOR()
        template_values.update(get_response_page_values(qp, bm.stop_numbers, bm.service_patterns, bm.time_limit))
        self.write_response('req', template_values)

class Error404Page(CustomRequestHandler):
    '''Not found error page request handler'''
    def get(self):
        self.error(404)
        template_values = {'message':'There is no such page on the site.'}
        self.write_response('404', template_values)
    
application = webapp.WSGIApplication(
    [('/', MainPage),
	('/req', ReqPage),
    ('/csv', CsvPage),
    ('/u/?', UserEditPage),
    ('/u/([^/]+)/?', UserBookmarksListPage),
    ('/u/([^/]+)/([^/]+)', UserBookmarkPage),
    ('/admin/secret', AdminPage),
    ('/.*', Error404Page)],
    debug=True)

def main():
    run_wsgi_app(application)

if __name__ == "__main__":
    main()
